1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.google.common.eventbus;
18
19 import com.google.common.collect.ImmutableList;
20 import com.google.common.collect.Lists;
21
22 import junit.framework.TestCase;
23
24 import java.util.Collection;
25 import java.util.List;
26 import java.util.Set;
27 import java.util.concurrent.ExecutorService;
28 import java.util.concurrent.Executors;
29 import java.util.concurrent.Future;
30 import java.util.concurrent.atomic.AtomicInteger;
31
32
33
34
35
36
37 public class EventBusTest extends TestCase {
38 private static final String EVENT = "Hello";
39 private static final String BUS_IDENTIFIER = "test-bus";
40
41 private EventBus bus;
42
43 @Override protected void setUp() throws Exception {
44 super.setUp();
45 bus = new EventBus(BUS_IDENTIFIER);
46 }
47
48 public void testBasicCatcherDistribution() {
49 StringCatcher catcher = new StringCatcher();
50 bus.register(catcher);
51 bus.post(EVENT);
52
53 List<String> events = catcher.getEvents();
54 assertEquals("Only one event should be delivered.", 1, events.size());
55 assertEquals("Correct string should be delivered.", EVENT, events.get(0));
56 }
57
58
59
60
61
62
63
64 public void testPolymorphicDistribution() {
65
66
67
68
69 StringCatcher stringCatcher = new StringCatcher();
70
71 final List<Object> objectEvents = Lists.newArrayList();
72 Object objCatcher = new Object() {
73 @SuppressWarnings("unused")
74 @Subscribe public void eat(Object food) {
75 objectEvents.add(food);
76 }
77 };
78
79 final List<Comparable<?>> compEvents = Lists.newArrayList();
80 Object compCatcher = new Object() {
81 @SuppressWarnings("unused")
82 @Subscribe public void eat(Comparable<?> food) {
83 compEvents.add(food);
84 }
85 };
86 bus.register(stringCatcher);
87 bus.register(objCatcher);
88 bus.register(compCatcher);
89
90
91 final Object OBJ_EVENT = new Object();
92 final Object COMP_EVENT = new Integer(6);
93
94 bus.post(EVENT);
95 bus.post(OBJ_EVENT);
96 bus.post(COMP_EVENT);
97
98
99 List<String> stringEvents = stringCatcher.getEvents();
100 assertEquals("Only one String should be delivered.",
101 1, stringEvents.size());
102 assertEquals("Correct string should be delivered.",
103 EVENT, stringEvents.get(0));
104
105
106 assertEquals("Three Objects should be delivered.",
107 3, objectEvents.size());
108 assertEquals("String fixture must be first object delivered.",
109 EVENT, objectEvents.get(0));
110 assertEquals("Object fixture must be second object delivered.",
111 OBJ_EVENT, objectEvents.get(1));
112 assertEquals("Comparable fixture must be thirdobject delivered.",
113 COMP_EVENT, objectEvents.get(2));
114
115
116 assertEquals("Two Comparable<?>s should be delivered.",
117 2, compEvents.size());
118 assertEquals("String fixture must be first comparable delivered.",
119 EVENT, compEvents.get(0));
120 assertEquals("Comparable fixture must be second comparable delivered.",
121 COMP_EVENT, compEvents.get(1));
122 }
123
124 public void testSubscriberThrowsException() throws Exception{
125 final RecordingSubscriberExceptionHandler handler =
126 new RecordingSubscriberExceptionHandler();
127 final EventBus eventBus = new EventBus(handler);
128 final RuntimeException exception =
129 new RuntimeException("but culottes have a tendancy to ride up!");
130 final Object subscriber = new Object() {
131 @Subscribe
132 public void throwExceptionOn(String message) {
133 throw exception;
134 }
135 };
136 eventBus.register(subscriber);
137 eventBus.post(EVENT);
138
139 assertEquals("Cause should be available.",
140 exception, handler.exception);
141 assertEquals("EventBus should be available.",
142 eventBus, handler.context.getEventBus());
143 assertEquals("Event should be available.",
144 EVENT,
145 handler.context.getEvent());
146 assertEquals("Subscriber should be available.",
147 subscriber, handler.context.getSubscriber());
148 assertEquals("Method should be available.",
149 subscriber.getClass().getMethod("throwExceptionOn", String.class),
150 handler.context.getSubscriberMethod());
151 }
152
153 public void testSubscriberThrowsExceptionHandlerThrowsException() throws Exception{
154 final EventBus eventBus = new EventBus(new SubscriberExceptionHandler() {
155 @Override
156 public void handleException(Throwable exception,
157 SubscriberExceptionContext context) {
158 throw new RuntimeException();
159 }
160 });
161 final Object subscriber = new Object() {
162 @Subscribe
163 public void throwExceptionOn(String message) {
164 throw new RuntimeException();
165 }
166 };
167 eventBus.register(subscriber);
168 try {
169 eventBus.post(EVENT);
170 } catch (RuntimeException e) {
171 fail("Exception should not be thrown.");
172 }
173 }
174
175 public void testDeadEventForwarding() {
176 GhostCatcher catcher = new GhostCatcher();
177 bus.register(catcher);
178
179
180 bus.post(EVENT);
181
182 List<DeadEvent> events = catcher.getEvents();
183 assertEquals("One dead event should be delivered.", 1, events.size());
184 assertEquals("The dead event should wrap the original event.",
185 EVENT, events.get(0).getEvent());
186 }
187
188 public void testDeadEventPosting() {
189 GhostCatcher catcher = new GhostCatcher();
190 bus.register(catcher);
191
192 bus.post(new DeadEvent(this, EVENT));
193
194 List<DeadEvent> events = catcher.getEvents();
195 assertEquals("The explicit DeadEvent should be delivered.",
196 1, events.size());
197 assertEquals("The dead event must not be re-wrapped.",
198 EVENT, events.get(0).getEvent());
199 }
200
201 public void testFlattenHierarchy() {
202 HierarchyFixture fixture = new HierarchyFixture();
203 Set<Class<?>> hierarchy = bus.flattenHierarchy(fixture.getClass());
204
205 assertEquals(5, hierarchy.size());
206 assertContains(Object.class, hierarchy);
207 assertContains(HierarchyFixtureInterface.class, hierarchy);
208 assertContains(HierarchyFixtureSubinterface.class, hierarchy);
209 assertContains(HierarchyFixtureParent.class, hierarchy);
210 assertContains(HierarchyFixture.class, hierarchy);
211 }
212
213 public void testMissingSubscribe() {
214 bus.register(new Object());
215 }
216
217 public void testUnregister() {
218 StringCatcher catcher1 = new StringCatcher();
219 StringCatcher catcher2 = new StringCatcher();
220 try {
221 bus.unregister(catcher1);
222 fail("Attempting to unregister an unregistered object succeeded");
223 } catch (IllegalArgumentException expected) {
224
225 }
226
227 bus.register(catcher1);
228 bus.post(EVENT);
229 bus.register(catcher2);
230 bus.post(EVENT);
231
232 List<String> expectedEvents = Lists.newArrayList();
233 expectedEvents.add(EVENT);
234 expectedEvents.add(EVENT);
235
236 assertEquals("Two correct events should be delivered.",
237 expectedEvents, catcher1.getEvents());
238
239 assertEquals("One correct event should be delivered.",
240 Lists.newArrayList(EVENT), catcher2.getEvents());
241
242 bus.unregister(catcher1);
243 bus.post(EVENT);
244
245 assertEquals("Shouldn't catch any more events when unregistered.",
246 expectedEvents, catcher1.getEvents());
247 assertEquals("Two correct events should be delivered.",
248 expectedEvents, catcher2.getEvents());
249
250 try {
251 bus.unregister(catcher1);
252 fail("Attempting to unregister an unregistered object succeeded");
253 } catch (IllegalArgumentException expected) {
254
255 }
256
257 bus.unregister(catcher2);
258 bus.post(EVENT);
259 assertEquals("Shouldn't catch any more events when unregistered.",
260 expectedEvents, catcher1.getEvents());
261 assertEquals("Shouldn't catch any more events when unregistered.",
262 expectedEvents, catcher2.getEvents());
263 }
264
265
266
267
268 public void testRegisterThreadSafety() throws Exception {
269 List<StringCatcher> catchers = Lists.newCopyOnWriteArrayList();
270 List<Future<?>> futures = Lists.newArrayList();
271 ExecutorService executor = Executors.newFixedThreadPool(10);
272 int numberOfCatchers = 10000;
273 for (int i = 0; i < numberOfCatchers; i++) {
274 futures.add(executor.submit(new Registrator(bus, catchers)));
275 }
276 for (int i = 0; i < numberOfCatchers; i++) {
277 futures.get(i).get();
278 }
279 assertEquals("Unexpected number of catchers in the list",
280 numberOfCatchers, catchers.size());
281 bus.post(EVENT);
282 List<String> expectedEvents = ImmutableList.of(EVENT);
283 for (StringCatcher catcher : catchers) {
284 assertEquals("One of the registered catchers did not receive an event.",
285 expectedEvents, catcher.getEvents());
286 }
287 }
288
289
290
291
292
293
294
295
296 public void testRegistrationWithBridgeMethod() {
297 final AtomicInteger calls = new AtomicInteger();
298 bus.register(new Callback<String>() {
299 @Subscribe
300 @Override
301 public void call(String s) {
302 calls.incrementAndGet();
303 }
304 });
305
306 bus.post("hello");
307
308 assertEquals(1, calls.get());
309 }
310
311 private <T> void assertContains(T element, Collection<T> collection) {
312 assertTrue("Collection must contain " + element,
313 collection.contains(element));
314 }
315
316
317
318
319 private static final class RecordingSubscriberExceptionHandler
320 implements SubscriberExceptionHandler {
321
322 public SubscriberExceptionContext context;
323 public Throwable exception;
324
325 @Override
326 public void handleException(Throwable exception,
327 SubscriberExceptionContext context) {
328 this.exception = exception;
329 this.context = context;
330 }
331 }
332
333
334
335
336
337 private static class Registrator implements Runnable {
338 private final EventBus bus;
339 private final List<StringCatcher> catchers;
340
341 Registrator(EventBus bus, List<StringCatcher> catchers) {
342 this.bus = bus;
343 this.catchers = catchers;
344 }
345
346 @Override
347 public void run() {
348 StringCatcher catcher = new StringCatcher();
349 bus.register(catcher);
350 catchers.add(catcher);
351 }
352 }
353
354
355
356
357
358
359
360 public static class GhostCatcher {
361 private List<DeadEvent> events = Lists.newArrayList();
362
363 @Subscribe
364 public void ohNoesIHaveDied(DeadEvent event) {
365 events.add(event);
366 }
367
368 public List<DeadEvent> getEvents() {
369 return events;
370 }
371 }
372
373 private interface HierarchyFixtureInterface {
374
375 }
376
377 private interface HierarchyFixtureSubinterface
378 extends HierarchyFixtureInterface {
379
380 }
381
382 private static class HierarchyFixtureParent
383 implements HierarchyFixtureSubinterface {
384
385 }
386
387 private static class HierarchyFixture extends HierarchyFixtureParent {
388
389 }
390
391 private interface Callback<T> {
392 void call(T t);
393 }
394 }